package com.xyy.saas.payment.manager;

import com.alibaba.fastjson.JSONObject;
import com.xyy.framework.redis.core.RedisClient;
import com.xyy.saas.payment.adpater.yzh.constant.ConfigPath;
import com.xyy.saas.payment.adpater.yzh.constant.DataDict;
import com.xyy.saas.payment.adpater.yzh.constant.XmlData;
import com.xyy.saas.payment.adpater.yzh.util.HttpUtil;
import com.xyy.saas.payment.adpater.yzh.util.YzhResultParseUtil;
import com.xyy.saas.payment.adpater.yzh.vo.BackNotifyData;
import com.xyy.saas.payment.adpater.yzh.vo.BackNotifyMessage;
import com.xyy.saas.payment.adpater.yzh.vo.BankCardOrder;
import com.xyy.saas.payment.adpater.yzh.vo.OrderQuery;
import com.xyy.saas.payment.adpater.yzh.vo.ThreeFactorVerify;
import com.xyy.saas.payment.callback.core.dto.CallbackMsg;
import com.xyy.saas.payment.callback.core.result.CallbackRunner;
import com.xyy.saas.payment.common.Constants;
import com.xyy.saas.payment.common.RocketMqTopic;
import com.xyy.saas.payment.common.exception.PaymentException;
import com.xyy.saas.payment.cores.bo.YzhBankPayBo;
import com.xyy.saas.payment.cores.enums.BankPayChannelEnum;
import com.xyy.saas.payment.cores.enums.BankPayStatusEnum;
import com.xyy.saas.payment.cores.enums.ResultCodeEnum;
import com.xyy.saas.payment.cores.enums.YzhBankPayStatusEnum;
import com.xyy.saas.payment.cores.vo.CallbackResultVo;
import com.xyy.saas.payment.cores.vo.ResultVO;
import com.xyy.saas.payment.cores.vo.YzhBankPayCallbackExtraVo;
import com.xyy.saas.payment.cores.vo.YzhBankPayVo;
import com.xyy.saas.payment.cores.vo.YzhVerifyBankcardVo;
import com.xyy.saas.payment.dao.model.BankPayRecord;
import com.xyy.saas.payment.service.BankPayRecordService;
import com.xyy.saas.payment.util.*;
import com.xyy.saas.payment.util.mq.MqProducer;
import com.xyy.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.producer.SendResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Objects;

/**
 * @Author lina
 * @Description
 * @Date 2022/3/9
 **/
@Slf4j
@Service
public class BankPayManager {
    private static final String BANK_PAY_REDIS_KEY = "ban_pay_";
    @Autowired
    private RedisClient redisClient;
    @Autowired
    private LeafUtil leafUtil;
    @Autowired
    private MqProducer mqProducer;
    @Autowired
    private BankPayRecordService bankPayRecordService;
    @Autowired
    private CallbackRunner callbackRunner;

    public ResultVO verifyBankCard(YzhVerifyBankcardVo verifyBankcardVo) {
        String paramJson = JSONUtils.toJSON(verifyBankcardVo);
        log.info("verifyBankcard-start: param:{}", paramJson);
        try {
            checkParam(verifyBankcardVo);
        } catch (PaymentException e) {
            //返回参数校验错误信息
            return ResultVO.createError(e.getErrCode(), e.getErrMsg());
        }
        try {
            ThreeFactorVerify threeFactorVerify = new ThreeFactorVerify(verifyBankcardVo.getRealName(), verifyBankcardVo.getIdCard(), verifyBankcardVo.getBankcardNo());
            Map<String, Object> result = HttpUtil.post(threeFactorVerify.assembleRequest(ConfigPath.HY_BANK_PAY), ApolloUtil.getProperty(ConfigPath.YZH_THREE_FACTOR_BANK_CARD_VERIFY));
            if (MapUtils.isEmpty(result)) {
                return ResultVO.createError("解析结果错误");
            }
            log.info("verifyBankcard-end: param:{}, result:{}", paramJson, JSONUtils.toJSON(result));
            Object resultCode = result.get(XmlData.STATUSCODE);
            // TODO: lina 2022/3/16 结果解析
            return DataDict.SUCCESS_CODE.equals(resultCode) ? ResultVO.createSuccess() : ResultVO.createError("校验失败");

        }catch (Exception e){
            log.error("verifyBankcard param:{}", verifyBankcardVo, e);
            return ResultVO.createError("系统异常");
        }
    }

    /**
     * 银行卡支付申请
     * @param bankPayVo
     * @return
     */
    public ResultVO<YzhBankPayBo> bankPay(YzhBankPayVo bankPayVo) {
        String paramJson = JSONUtils.toJSON(bankPayVo);
        log.info("bankPay-start: param:{}", paramJson);
        try {
            //1:校验参数
            checkParam(bankPayVo);
        } catch (PaymentException e) {
            log.warn("bankPay参数异常: param:{}", paramJson);
            return ResultVO.createError(e.getErrCode(), e.getErrMsg());
        }
        try {
            //2:用户id加锁
            if (!StringUtil.isEmpty(redisClient.get(BANK_PAY_REDIS_KEY + bankPayVo.getUserId()))){
                return ResultVO.create(ResultCodeEnum.NOT_CONTINUOUS_CLICK);
            }
            try {
                redisClient.set(BANK_PAY_REDIS_KEY + bankPayVo.getUserId(), "1", 5L);
            } catch (Exception e) {
                return ResultVO.createError("加锁处理失败，稍后重试");
            }

            //3:查询是否有记录
            BankPayRecord bankPayRecord = bankPayRecordService.queryByBusinessNo(bankPayVo.getBizNo());
            if (Objects.nonNull(bankPayRecord)) {
                log.info("bankPay：记录已存在:bankPayRecord:{}", JSONObject.toJSONString(bankPayRecord));
                return ResultVO.createSuccess(YzhBankPayBo
                        .builder()
                        .bankPayStatusEnum(BankPayStatusEnum.fromCode(bankPayRecord.getStatus()))
                        .payNo(bankPayRecord.getPayNo())
                        .bizNo(bankPayRecord.getBusinessNo())
                        .message(bankPayRecord.getRemark())
                        .finishedTime(bankPayRecord.getFinishedTime())
                        .build());
            }

            //4-1:生成支付流水
            String payNo = leafUtil.getSnowflakeId();
            BankPayRecord waitSaveRecord = buildBankPayRecord(bankPayVo, payNo);
            log.info("bankPay：waitSaveRecord:{}", JSONObject.toJSONString(waitSaveRecord));

            //4-2:保存流水
            boolean saveResult  = bankPayRecordService.save(waitSaveRecord) > 0;
            log.info("bankPay：saveRecordResult:{}", saveResult);
            if (!saveResult) {
                return ResultVO.createError("数据接收失败");
            }
            //5: 发mq申请云账户转账
            log.info("bankPay# send mq(topic_async_withdraw). send data payNo:{}", payNo);
            try {
                SendResult sendResult = mqProducer.syncSendMsg(RocketMqTopic.TOPIC_ASYNC_BANK_PAY, payNo);
                log.info("bankPay# send mq(topic_async_withdraw)-result:{}", JSONUtils.toJSON(sendResult));
            } catch (Exception e) {
                log.error("bankPay# send mq(topic_async_withdraw)失败. send data payNo:{}", payNo, e);
            }
            return ResultVO.createSuccess(YzhBankPayBo.builder().bankPayStatusEnum(BankPayStatusEnum.RECEIVED).payNo(payNo).build());
        }catch (Exception e){
            log.error("bankPay param:{}", paramJson, e);
            return ResultVO.create(ResultCodeEnum.DEFAULT_ERROR);
        }
    }

    /**
     * 查询云账户侧，支付状态
     * @param payNo
     * @return
     */
    public BackNotifyData queryPayResultFromYzh(String payNo, String method) {
        OrderQuery orderQuery = new OrderQuery(payNo, method);
        try {
            Map<String, Object> result = HttpUtil.get(orderQuery.assembleRequest(method), ApolloUtil.getProperty(ConfigPath.YZH_ORDER_QUERY), method);
            log.info("queryPayResultFromYzh:result:{}", JSONUtils.toJSON(result));
            return buildBankPayVo(result, method);
        } catch (Exception e) {
            log.error("queryPayResultFromYzh查询异常 payNo:{} method:{}", payNo, method, e);
            return null;
        }
    }

    private BackNotifyData buildBankPayVo(Map<String, Object> result, String method) {
        return YzhResultParseUtil.parsePayQueryResult(result, method);
    }

    /**
     * 校验银行三要素参数
     * @param verifyBankcardVo
     * @throws PaymentException
     */
    private void checkParam(YzhVerifyBankcardVo verifyBankcardVo) throws PaymentException{
        if (Objects.isNull(verifyBankcardVo)) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "参数为空");
        }
        if (StringUtils.isBlank(verifyBankcardVo.getBankcardNo())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "银行卡号为空");
        }
        if (StringUtils.isBlank(verifyBankcardVo.getIdCard())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "身份证为空");
        }
        if (StringUtils.isBlank(verifyBankcardVo.getRealName())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "真实姓名为空");
        }
    }

    /**
     * 构建流水
     * @param bankPayVo
     * @param payNo
     * @return
     */
    public static BankPayRecord buildBankPayRecord(YzhBankPayVo bankPayVo, String payNo) {
        BankPayRecord bankPayRecord = new BankPayRecord();
        bankPayRecord.setBusinessNo(bankPayVo.getBizNo());
        bankPayRecord.setPayNo(payNo);
        bankPayRecord.setAmount(bankPayVo.getAmount());
        bankPayRecord.setBankCardNo(bankPayVo.getBankcardNo());
        bankPayRecord.setIdCardNo(bankPayVo.getIdCard());
        bankPayRecord.setRealName(bankPayVo.getRealName());
        bankPayRecord.setMobile(bankPayVo.getMobile());
        bankPayRecord.setStatus(BankPayStatusEnum.RECEIVED.getStatus());
        bankPayRecord.setRemark(bankPayVo.getRemark());
        bankPayRecord.setPayChannel(null != bankPayVo.getBankPayChannel() ?
                bankPayVo.getBankPayChannel().getCode() :
                BankPayChannelEnum.YUN_ZHANG_HU.getCode());
        bankPayRecord.setMerchantId(ApolloUtil.getProperty(ConfigPath.YZH_DEALERID));
        bankPayRecord.setPayee(bankPayVo.getUserId());
        bankPayRecord.setCallback(bankPayVo.getCallback());
        bankPayRecord.setCreateTime(LocalDateTime.now());
        bankPayRecord.setUpdateTime(LocalDateTime.now());
        return bankPayRecord;
    }

    /**
     * 构建转账参数
     * @param bankPayRecord
     * @return
     */
    public static BankCardOrder buildBankCardOrder(BankPayRecord bankPayRecord) {
        return new BankCardOrder(
                bankPayRecord.getPayNo(),
                bankPayRecord.getRealName(),
                bankPayRecord.getBankCardNo(),
                bankPayRecord.getIdCardNo(),
                bankPayRecord.getMobile(),
                bankPayRecord.getAmount(),
                bankPayRecord.getRemark());
    }



    /**
     * 校验转账参数
     * @param bankPayVo
     * @throws PaymentException
     */
    private void checkParam(YzhBankPayVo bankPayVo) throws PaymentException{
        if (Objects.isNull(bankPayVo)) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "参数为空");
        }
        if (StringUtils.isBlank(bankPayVo.getBankcardNo())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "银行卡号为空");
        }
        if (bankPayVo.getAmount() < 1) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "金额必须大于1分钱");
        }
        if (StringUtils.isBlank(bankPayVo.getIdCard())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "身份证为空");
        }
        if (StringUtils.isBlank(bankPayVo.getRealName())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "真实姓名为空");
        }
        if (StringUtils.isBlank(bankPayVo.getBizNo())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "业务号为空");
        }
        if (StringUtils.isBlank(bankPayVo.getCallback())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "回调为空");
        }
        if (StringUtils.isBlank(bankPayVo.getUserId())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "用户id为空");
        }
        if (StringUtils.isNotBlank(bankPayVo.getRemark()) && bankPayVo.getRemark().length() > Constants.MAX_STRING_LENGTH_PRODUCT_NAME) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "备注信息不得超过20个字符");
        }
    }

    /**
     * 根据支付流水号更新状态
     * @param payNo
     * @param statusEnum
     * @param refNo
     * @return
     */
    public boolean updateBankPayStatus(String payNo, BankPayStatusEnum statusEnum, String refNo, String failMsg) {
        log.info("BankPayManager#updateBankPayStatus:payNo:{}, status:{}，refNo:{}", payNo, statusEnum, refNo);
        if (StringUtils.isBlank(payNo) || null == statusEnum) {
            return false;
        }
        BankPayRecord payRecord = new BankPayRecord();
        payRecord.setPayNo(payNo);
        payRecord.setUpdateTime(LocalDateTime.now());
        payRecord.setStatus(statusEnum.getStatus());
        payRecord.setRefNo(refNo);
        payRecord.setRemark(failMsg);
        log.info("BankPayManager#updateBankPayStatus:updateVo:{}", JSONUtils.toJSON(payRecord));
        return bankPayRecordService.updateByPayNo(payRecord) > 0;
    }

    public boolean dealYzhBankPayCallback(BackNotifyMessage backNotifyMessage) throws PaymentException {
        log.info("dealYzhBankPayCallback#backNotifyMessage={}", JSONObject.toJSONString(backNotifyMessage));
        BackNotifyData callBackResult = backNotifyMessage.getData();
        //1:校验回调参数
        checkBackNotifyData(callBackResult);
        //2:根据订单号查询记录
        String payNo = callBackResult.getOrder_id();
        BankPayRecord bankPayRecord = bankPayRecordService.queryByPayNo(payNo);
        if (Objects.isNull(bankPayRecord)) {
            log.error("dealYzhBankPayCallback#未查到流水#payNo:{}", payNo);
            return false;
        }
        log.info("dealYzhBankPayCallback#流水信息：{}", JSONUtils.toJSON(bankPayRecord));
        //3:解析状态和更新字段
        BankPayRecord waitUpdateRecord = buildWaitUpdateRecord(bankPayRecord, callBackResult);
        if (null == waitUpdateRecord) {
            return false;
        }
        boolean updateResult = bankPayRecordService.updateByPayNo(waitUpdateRecord) > 0;
        log.info("dealYzhBankPayCallback#更新流水信息#waitUpdateRecord{}， result:{}", JSONUtils.toJSON(waitUpdateRecord), updateResult);
        if (!updateResult) {
            return false;
        }
        //4:发送回调MQ
        bankPayCallback(waitUpdateRecord.getStatus(), bankPayRecord, callBackResult.getStatus_detail(), waitUpdateRecord.getFinishedTime());
        return true;
    }

    /**
     *
     * @param status 支付侧，转账状态
     * @param bankPayRecord 流水信息
     * @param code 三方code
     * @param finishTime 完成时间
     */
    public void bankPayCallback(Integer status, BankPayRecord bankPayRecord, String code, LocalDateTime finishTime) {
        if (!needCallback(status)) {
            log.info("bankPayCallback:不需要回调的状态:status:{}, payNo:{}", status, bankPayRecord.getPayNo());
            return;
        }
        callbackRunner.callback(CallbackMsg.build(
                getCallbackType(status),
                bankPayRecord.getCallback(),
                bankPayRecord.getPayNo(),
                bankPayRecord.getBusinessNo(),
                code,
                //完成时间，时间戳转换
                null != finishTime ?
                        finishTime.toInstant(ZoneOffset.of("+8")).toEpochMilli() :
                        System.currentTimeMillis()));
    }
    private static String getCallbackType(Integer status) {
        BankPayStatusEnum bankPayStatusEnum = BankPayStatusEnum.fromCode(status);
        if (null == bankPayStatusEnum) {
            return "";
        }
        switch (bankPayStatusEnum) {
            case SUCCESS:
                return CallbackResultVo.TYPE_BANK_PAY_SUCCESS;
            case FAIL:
                return CallbackResultVo.TYPE_BANK_PAY_FAIL;
            case REFUNDED:
                return CallbackResultVo.TYPE_BANK_PAY_REFUND;
            default:
                return "";
        }
    }

    /**
     * 是否需要回调业务
     * @param status
     * @return
     */
    private boolean needCallback(int status) {
        BankPayStatusEnum bankPayStatusEnum = BankPayStatusEnum.fromCode(status);
        return BankPayStatusEnum.FAIL == bankPayStatusEnum ||
                BankPayStatusEnum.SUCCESS == bankPayStatusEnum ||
                BankPayStatusEnum.REFUNDED == bankPayStatusEnum;
    }

    private BankPayRecord buildWaitUpdateRecord(BankPayRecord bankPayRecord, BackNotifyData callBackResult) {
        YzhBankPayStatusEnum yzhBankPayStatusEnum = YzhBankPayStatusEnum.fromCode(callBackResult.getStatus());
        if (null == yzhBankPayStatusEnum) {
            log.error("dealYzhBankPayCallback#不能识别的回调状态，不做处理");
            return null;
        }
        //回调流水状态
        BankPayStatusEnum backStatus = yzhBankPayStatusEnum.getBankPayStatus();
        //当前流水状态
        BankPayStatusEnum nowStatus = BankPayStatusEnum.fromCode(bankPayRecord.getStatus());
        //当前流水状态 与 回调流水状态比较
        if (null == nowStatus || nowStatus == backStatus) {
            log.info("dealYzhBankPayCallback#buildWaitUpdateRecord状态一致，不做处理");
            return null;
        }
        if (!nowStatus.getNextStatus().contains(backStatus.getStatus())) {
            log.info("dealYzhBankPayCallback#buildWaitUpdateRecord状态回溯，不做处理");
            return null;
        }
        BankPayRecord updatePayRecord = new BankPayRecord();
        updatePayRecord.setPayNo(bankPayRecord.getPayNo());
        updatePayRecord.setUpdateTime(LocalDateTime.now());
        updatePayRecord.setStatus(backStatus.getStatus());
        updatePayRecord.setRemark(callBackResult.getStatus_detail_message());
        //解析完成时间
        if (StringUtils.isNotBlank(callBackResult.getFinished_time())) {
            LocalDateTime finishedTime = null;
            try {
                finishedTime = LocalDateTime.parse(callBackResult.getFinished_time(),
                        DateTimeFormatter.ofPattern(DateUtil.DATEFORMATSECOND));
            } catch (Exception e) {
                log.info("dealYzhBankPayCallback#buildWaitUpdateRecord解析完成时间失败", e);
                finishedTime = LocalDateTime.now();
            }
            updatePayRecord.setFinishedTime(finishedTime);
        }
        //构建扩展字段
        YzhBankPayCallbackExtraVo extraVo = new YzhBankPayCallbackExtraVo(
                AmountCastUtil.castY2F(callBackResult.getBroker_fee()),
                AmountCastUtil.castY2F(callBackResult.getBroker_real_fee()),
                AmountCastUtil.castY2F(callBackResult.getBroker_deduct_fee()
                ));
        updatePayRecord.setExtra(JSONUtils.toJSON(extraVo));
        return updatePayRecord;
    }

    private static void checkBackNotifyData(BackNotifyData callBackResult) throws PaymentException{
        log.info("checkBackNotifyData:callBackResult:{}", JSONUtils.toJSON(callBackResult));
        if (Objects.isNull(callBackResult)) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "data为空");
        }
        if (StringUtils.isBlank(callBackResult.getOrder_id())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "Order_id为空");
        }
        if (StringUtils.isBlank(callBackResult.getStatus())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR, "status为空");
        }
    }
}
